
Introduction
'madxlib' is a source code for creating a Windows DLL, that performs buffer-based MP3 decoding. It allows you to build interfaces that can process a given MP3 file in chunks at the intervals you specify, and returns the decoded 16-bit PCM samples in a buffer.
Background
madxlib effectively supersedes madlldlib, another MP3 decoding source I have released via The Code Project. The two sources differ in that, madlldlib took as parameters the location of the input MP3 and output WAV/PCM files and performed the conversion while passing status back to the calling code. This gave little control to the calling code to pause, restart, queue the samples, etc. madxlib rectifies this by relying on the calling code to fill the input buffer (which can come from a local file, the Internet, network share, etc). It then returns an output buffer of the converted PCM samples and waits for the next input.
This source, like madlldlib, was based on the madlld source, which was designed as a tutorial of the low-level functions of the libmad library.
Using the Code
Included in the source is the file test.cpp. This is a simple example demonstrating how to program the madxlib API. It consists of a single main()
function.
Within main()
, the variable declarations are first made:
unsigned char in_buffer[MADX_INPUT_BUFFER_SIZE + MAD_BUFFER_GUARD];
unsigned char out_buffer[MADX_OUTPUT_BUFFER_SIZE];
size_t a;
size_t in_size = MADX_INPUT_BUFFER_SIZE + MAD_BUFFER_GUARD;
madx_house mxhouse;
madx_stat mxstat;
madx_sig mxsig;
in_buffer
and out_buffer
will be used to send the MP3 data to madxlib and retrieve the PCM samples once they've been decoded, respectively. There are three datatypes specific to madxlib. madx_house
"houses" the necessary data types that make madxlib (and the underlying library libmad
) work. madx_stat
is used to send status and information, such as how many bytes to read from the input, between the calling code and madxlib. madx_sig
is an enum
type that is returned by the madx_read()
function (described below) to signal what action your code should take.
Next, madx_init()
is executed.
madx_init(out_buffer, &mxhouse);
madx_init()
takes as parameters a pointer to the output buffer, and the address of the madx_house
structure, mxhouse
. It then initializes the necessary libmad
structures and assigns the out_buffer
pointer to an internal variable.
Next test.cpp opens its input and output files with fopen()
, erroring if there is a problem. Now we are ready to start the main loop (and most important logic) of the program.
do
{
mxsig = madx_read( in_buffer, out_buffer, &mxhouse, &mxstat );
if (strcmp(mxstat.msg, "")) printf("%s\n", mxstat.msg);
if (mxsig == ERROR_OCCURED)
{
printf("Unrecoverable error %s\n", mxstat.msg);
break;
}
else if (mxsig == MORE_INPUT)
{
if (mxstat.buffstart)
{
if ( (a = fread(mxstat.buffstart, 1,
mxstat.readsize, in_file))== mxstat.readsize)
{
printf("Filling buffer.\n");
}
else if (feof(in_file))
{
mxstat.is_eof = 1;
mxstat.readsize = a;
}
else
printf("Error! %d\n", ferror(in_file));
}
else
{
if ( fread(in_buffer, 1, mxstat.readsize, in_file) ==
mxstat.readsize)
{
printf("Fill buffer full.\n");
}
else if (feof(in_file))
{
mxstat.is_eof = 1;
mxstat.readsize = a;
}
else
printf("Error! %d\n", ferror(in_file));
}
}
else if (mxsig == FLUSH_BUFFER)
{
if (fwrite(out_buffer, 1, mxstat.write_size, out_file)
== mxstat.write_size)
printf("Writing buffer.\n");
else
printf("fwrite() error\n");
}
else if (mxsig == EOF_REACHED)
{
if ( (a = fwrite(out_buffer,1,mxstat.write_size,out_file))
!= mxstat.write_size)
{
printf("Error with final write! out_size:%d, fwrite: %d\n",
mxstat.write_size, a);
}
else
printf("Finished. out_size:%d, fwrite: %d\n",
mxstat.write_size, a);
break;
}
}
while(1);
The above do...while
loop is the bulk of test.cpp. For every iteration, it makes a call to madx_read()
. madx_read()
takes as parameters the input buffer (initially empty), the output buffer, the address of mxhouse
(the madx_house
structure), and the address of mxstat
(the madx_stat
structure). It returns a madx_sig
value, which is stored in the variable mxsig
.
madx_read()
essentially does the following. It decodes the MP3 samples in the input buffer to raw audio (PCM) and synthesizes them, then scales the samples down to 16-bit. Depending on what is encountered during the execution of the function (error, more input is needed, output buffer is full), the appropriate madx_sig
signal is returned, and the calling code (i.e. this loop) must handle the signal. The trickiest part of this is handling the partial reads when the buffer fills up. This is where madx_stat
comes in.
After madx_read()
returns, mxsig
is inspected to determine what action should be taken. If an ERROR_OCCURED
, it prints an appropriate message and breaks out of the loop. If the instruction is FLUSH_BUFFER
, then this means that the output buffer (out_buffer
) is full and ready to be written, in this case, to disk. Note the use of mxstat.write_size
in the fwrite()
statement. mxstat.write_size
will always contain the correct number of units to write. When a FLUSH_BUFFER
has been returned, we do not exit the loop. We only exit if ERROR_OCCURED
or EOF_REACHED
. If the latter occured, then we must write the remaining bytes in out_buffer
to the file and break the loop.
If MORE_INPUT
is signaled, then the calling code must refill the input buffer. First, this code segment (else if mxsig == MORE_INPUT
above) checks the mxstat.buffstart
variable. If it is 0, then a full buffer read is done, which fills the buffer from the first position to mxstat.readsize
. mxstat.readsize
is always set by madx_read()
before returning MORE_INPUT
. If mxstat.buffstart
is not 0, then it means you must fill the input buffer, but not from the first position. In this fread()
statement, the input buffer in_buffer
is substituted for mxstat.buffstart
, which is set by madx_read()
to some point between the starting position of in_buffer
and the end.
Note that after both reads EOF is checked, and if reached the variable mxstat.is_eof
is set to 1 (or "true"). This must be done so that madx_read()
can pad the final bytes before processing them. This is a specific requirement of the underlying libmad
library.
All that remains is to pass the &mxhouse
to madx_deinit()
, which takes care of freeing memory used by the libmad
structures. We then close out_file
and our task has been completed.
madx_deinit(&mxhouse);
fclose(out_file);
return;
Notes
- The source provided above depends on the libmad source. It must be downloaded and compiled first. See the notes in the file "Makefile" in madxlib for further instructions on compiling.
- This is a Beta release of madxlib. You should always check my website for the latest updates. They will be there first. In time I should have a forum dedicated to madxlib for answering questions.
- Also included in the demo is a C# sample that uses P/Invoke to access madxlib.dll.